Skip to content

Fix #4404: In LambdaLift, modify flags properly #4804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 20, 2018
Merged

Fix #4404: In LambdaLift, modify flags properly #4804

merged 1 commit into from
Jul 20, 2018

Conversation

Medowhill
Copy link
Contributor

No description provided.

Copy link
Member

@dottybot dottybot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, and thank you for opening this PR! 🎉

All contributors have signed the CLA, thank you! ❤️

Have an awesome day! ☀️

@@ -338,11 +338,16 @@ object LambdaLift {
else (topClass, JavaStatic)
}
else (lOwner, EmptyFlags)
// drop Module because class is no longer a singleton in the lifted context.
val nFlags = local.flags &~ Module | Lifted | maybeStatic
// keep Public when it is a local class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do local classes need to be public and classes lifted into traits non-final?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the issue #4404, @mbloms said:

Anonymous classes are compiled without the public final.

Therefore, I tried to keep anonymous classes as public after lifting. However, I did not consider non-anonymous local classes. I think non-anonymous local classes should not be public. I'll correct the code to distinguish anonymous and non-anonymous classes.

At the first, I changed the code simply to keep final always. Then, test t4565_1 failed. It was compiled but during the execution, there was an exception:
Exception in thread "main" java.lang.ClassFormatError: Method A$1 in class T has illegal modifiers: 0x1A
According to the JVM specification, a method declared inside interface cannot have the final flag, so that I thought that final should be dropped when a method is lifted into a trait.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think leaving the final flag for anonymous/local classes makes sense, but they shouldn't be public: they should never be called by external code.

According to the JVM specification, a method declared inside interface cannot have the final flag,

I see! Can you add this information as a comment in the code? (With a link to the relevant part of the spec)

@smarter
Copy link
Member

smarter commented Jul 18, 2018

Otherwise, this looks great but is missing tests. You could either add a file in tests/run that uses Java reflection to check flags, or add tests in https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala to check the flags of the emitted bytecode. If you're not sure how to do that, come to http://gitter.im/lampepfl/dotty and we'll help!

@Medowhill
Copy link
Contributor Author

I changed the code to add the private flag always for both methods and classes during the lifting.
I added comments about the JVM specification with its link.
Three tests using the Java reflection are added. The first and second tests are from issue #4404 and the last one is a test to check whether the final flag of a method is dropped correctly when it is lifted into a trait.

@allanrenucci
Copy link
Contributor

@Medowhill Can you rebase the PR on master and remove the merge commits?

@Medowhill
Copy link
Contributor Author

Medowhill commented Jul 19, 2018

I removed the merge commits.

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM

// According to the JVM specification, a method declared inside interface cannot have the final flag.
// "Methods of interfaces may have any of the flags in Table 4.6-A set except ACC_PROTECTED, ACC_FINAL, ..."
// (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6)
val initFlags = if (!local.isClass && (newOwner.flags is Trait)) preInitFlags &~ Final else preInitFlags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you test local.is(Method) instead of !local.isClass?

Copy link
Contributor

@allanrenucci allanrenucci Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal taste: I think it is clearer to use a var rather than coming up with new unique names. Ie.

var initFlags = ...
if (...) initFlags = initFlags &~ Final

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your suggestion is better than my original code. I'll fix it. Thank you!

}
}

object Test {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what you are testing here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trait T {
  def f = {
    object O
  }
}

When we compile the above code, the compiler will generate final lazy <accessor> module def O(): O$ inside method f. I tried to check whether the final flag of method O is dropped correctly during the LambdaLift phase through the test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to test that methods lose their final modifier, I would replace the object by a normal def. I think it is clearer than using an object relying on the fact that it desugars to a lazy val which itself desugars to a method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be clearer. I'll change it. Thank you!


trait T {
def f = {
final def f0 = ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the final modifier since I believe being able to write it in the first place is a bug?

Copy link
Contributor Author

@Medowhill Medowhill Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, do I need to change the test to use object inside a method?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I think the test is fine as is. We want to make sure that we don't add the final modifier in this case

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Can you squash your commits?

@allanrenucci allanrenucci merged commit 0672b6d into scala:master Jul 20, 2018
@allanrenucci
Copy link
Contributor

Thanks! 🎉

@Medowhill Medowhill deleted the issue4404 branch July 20, 2018 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants